
#include "asm/disassembler.h"
#include "os/os.h"

namespace l8
{

enum OperandOrder
{
    UNSET_OP_ORDER = 0,
    REG_OPER_OP_ORDER,
    OPER_REG_OP_ORDER
};


//------------------------------------------------------------------
// Tables
//------------------------------------------------------------------
struct ByteMnemonic
{
    int b;  // -1 terminates, otherwise must be in range (0..255)
    const char* mnem;
    OperandOrder op_order_;
};


static ByteMnemonic two_operands_instr[] =
{
    {0x03, "add", REG_OPER_OP_ORDER},
    {0x21, "and", OPER_REG_OP_ORDER},
    {0x23, "and", REG_OPER_OP_ORDER},
    {0x3B, "cmp", REG_OPER_OP_ORDER},
    {0x8D, "lea", REG_OPER_OP_ORDER},
    {0x09, "or", OPER_REG_OP_ORDER},
    {0x0B, "or", REG_OPER_OP_ORDER},
    {0x1B, "sbb", REG_OPER_OP_ORDER},
    {0x29, "sub", OPER_REG_OP_ORDER},
    {0x2B, "sub", REG_OPER_OP_ORDER},
    {0x85, "test", REG_OPER_OP_ORDER},
    {0x31, "xor", OPER_REG_OP_ORDER},
    {0x33, "xor", REG_OPER_OP_ORDER},
    {0x87, "xchg", REG_OPER_OP_ORDER},
    {0x8A, "mov_b", REG_OPER_OP_ORDER},
    {0x8B, "mov", REG_OPER_OP_ORDER},
    {-1, "", UNSET_OP_ORDER}
};


static ByteMnemonic zero_operands_instr[] = {
    {0xC3, "ret", UNSET_OP_ORDER},
    {0xC9, "leave", UNSET_OP_ORDER},
    {0x90, "nop", UNSET_OP_ORDER},
    {0xF4, "hlt", UNSET_OP_ORDER},
    {0xCC, "int3", UNSET_OP_ORDER},
    {0x60, "pushad", UNSET_OP_ORDER},
    {0x61, "popad", UNSET_OP_ORDER},
    {0x9C, "pushfd", UNSET_OP_ORDER},
    {0x9D, "popfd", UNSET_OP_ORDER},
    {0x9E, "sahf", UNSET_OP_ORDER},
    {0x99, "cdq", UNSET_OP_ORDER},
    {0x9B, "fwait", UNSET_OP_ORDER},
    {-1, "", UNSET_OP_ORDER}
};


static ByteMnemonic call_jump_instr[] = {
    {0xE8, "call", UNSET_OP_ORDER},
    {0xE9, "jmp", UNSET_OP_ORDER},
    {-1, "", UNSET_OP_ORDER}
};


static ByteMnemonic short_immediate_instr[] = {
    {0x05, "add", UNSET_OP_ORDER},
    {0x0D, "or", UNSET_OP_ORDER},
    {0x15, "adc", UNSET_OP_ORDER},
    {0x25, "and", UNSET_OP_ORDER},
    {0x2D, "sub", UNSET_OP_ORDER},
    {0x35, "xor", UNSET_OP_ORDER},
    {0x3D, "cmp", UNSET_OP_ORDER},
    {-1, "", UNSET_OP_ORDER}
};


static const char* jump_conditional_mnem[] = {
    /*0*/ "jo", "jno", "jc", "jnc",
          /*4*/ "jz", "jnz", "jna", "ja",
                /*8*/ "js", "jns", "jpe", "jpo",
                      /*12*/ "jl", "jnl", "jng", "jg"
                         };


static const char* set_conditional_mnem[] = {
    /*0*/ "seto", "setno", "setc", "setnc",
          /*4*/ "setz", "setnz", "setna", "seta",
                /*8*/ "sets", "setns", "setpe", "setpo",
                      /*12*/ "setl", "setnl", "setng", "setg"
                         };


enum InstructionType {
    NO_INSTR,
    ZERO_OPERANDS_INSTR,
    TWO_OPERANDS_INSTR,
    JUMP_CONDITIONAL_SHORT_INSTR,
    REGISTER_INSTR,
    MOVE_REG_INSTR,
    CALL_JUMP_INSTR,
    SHORT_IMMEDIATE_INSTR
};


struct InstructionDesc {
    const char* mnem;
    InstructionType type;
    OperandOrder op_order_;
};


class InstructionTable {
public:
    InstructionTable();
    const InstructionDesc& Get(byte x) const { return instructions_[x]; }

private:
    InstructionDesc instructions_[256];
    void Clear();
    void Init();
    void CopyTable(ByteMnemonic bm[], InstructionType type);
    void SetTableRange(InstructionType type,
                       byte start,
                       byte end,
                       const char* mnem);
    void AddJumpConditionalShort();
};


InstructionTable::InstructionTable() {
    Clear();
    Init();
}


void InstructionTable::Clear() {
    for (int i = 0; i < 256; i++) {
        instructions_[i].mnem = "";
        instructions_[i].type = NO_INSTR;
        instructions_[i].op_order_ = UNSET_OP_ORDER;
    }
}


void InstructionTable::Init() {
    CopyTable(two_operands_instr, TWO_OPERANDS_INSTR);
    CopyTable(zero_operands_instr, ZERO_OPERANDS_INSTR);
    CopyTable(call_jump_instr, CALL_JUMP_INSTR);
    CopyTable(short_immediate_instr, SHORT_IMMEDIATE_INSTR);
    AddJumpConditionalShort();
    SetTableRange(REGISTER_INSTR, 0x40, 0x47, "inc");
    SetTableRange(REGISTER_INSTR, 0x48, 0x4F, "dec");
    SetTableRange(REGISTER_INSTR, 0x50, 0x57, "push");
    SetTableRange(REGISTER_INSTR, 0x58, 0x5F, "pop");
    SetTableRange(REGISTER_INSTR, 0x91, 0x97, "xchg eax,");  // 0x90 is nop.
    SetTableRange(MOVE_REG_INSTR, 0xB8, 0xBF, "mov");
}


void InstructionTable::CopyTable(ByteMnemonic bm[], InstructionType type) {
    for (int i = 0; bm[i].b >= 0; i++) {
        InstructionDesc* id = &instructions_[bm[i].b];
        id->mnem = bm[i].mnem;
        id->op_order_ = bm[i].op_order_;
        assert(id->type == NO_INSTR);  // Information already entered
        id->type = type;
    }
}


void InstructionTable::SetTableRange(InstructionType type,
                                     byte start,
                                     byte end,
                                     const char* mnem) {
    for (byte b = start; b <= end; b++) {
        InstructionDesc* id = &instructions_[b];
        assert(id->type == NO_INSTR);  // Information already entered
        id->mnem = mnem;
        id->type = type;
    }
}


void InstructionTable::AddJumpConditionalShort() {
    for (byte b = 0x70; b <= 0x7F; b++) {
        InstructionDesc* id = &instructions_[b];
        assert(id->type == NO_INSTR);  // Information already entered
        id->mnem = jump_conditional_mnem[b & 0x0F];
        id->type = JUMP_CONDITIONAL_SHORT_INSTR;
    }
}


static InstructionTable instruction_table;


// The IA32 disassembler implementation.
class DisassemblerIA32
{
public:
    explicit DisassemblerIA32(const NameConverter& converter, bool abort_on_unimplemented = true)
            : converter_(converter)
            , tmp_buffer_pos_(0)
            , abort_on_unimplemented_(abort_on_unimplemented)
    {
        tmp_buffer_[0] = '\0';
    }

    virtual ~DisassemblerIA32() {}

    // Writes one disassembled instruction into 'buffer' (0-terminated).
    // Returns the length of the disassembled machine instruction in bytes.
    int InstructionDecode(Vector<char> buffer, byte* instruction);

private:
    DISALLOW_IMPLICIT_CONSTRUCTORS(DisassemblerIA32);

    const NameConverter& converter_;
    EmbeddedVector<char, 128> tmp_buffer_;
    unsigned int tmp_buffer_pos_;
    bool abort_on_unimplemented_;


    enum
    {
        eax = 0,
        ecx = 1,
        edx = 2,
        ebx = 3,
        esp = 4,
        ebp = 5,
        esi = 6,
        edi = 7
    };


    const char* NameOfCPURegister(int reg) const
    {
        return converter_.NameOfCPURegister(reg);
    }


    const char* NameOfByteCPURegister(int reg) const
    {
        return converter_.NameOfByteCPURegister(reg);
    }


    const char* NameOfXMMRegister(int reg) const {
        return converter_.NameOfXMMRegister(reg);
    }


    const char* NameOfAddress(byte* addr) const {
        return converter_.NameOfAddress(addr);
    }


    // Disassembler helper functions.
    static void get_modrm(byte data, int* mod, int* regop, int* rm) {
        *mod = (data >> 6) & 3;
        *regop = (data & 0x38) >> 3;
        *rm = data & 7;
    }


    static void get_sib(byte data, int* scale, int* index, int* base) {
        *scale = (data >> 6) & 3;
        *index = (data >> 3) & 7;
        *base = data & 7;
    }

    typedef const char* (DisassemblerIA32::*RegisterNameMapping)(int reg) const;

    int PrintRightOperandHelper(byte* modrmp, RegisterNameMapping register_name);
    int PrintRightOperand(byte* modrmp);
    int PrintRightByteOperand(byte* modrmp);
    int PrintOperands(const char* mnem, OperandOrder op_order, byte* data);
    int PrintImmediateOp(byte* data);
    int F7Instruction(byte* data);
    int D1D3C1Instruction(byte* data);
    int JumpShort(byte* data);
    int JumpConditional(byte* data, const char* comment);
    int JumpConditionalShort(byte* data, const char* comment);
    int SetCC(byte* data);
    int FPUInstruction(byte* data);
    void AppendToBuffer(const char* format, ...);


    void UnimplementedInstruction() {
        if (abort_on_unimplemented_) {
            UNIMPLEMENTED();
        } else {
            AppendToBuffer("'Unimplemented Instruction'");
        }
    }
};


void DisassemblerIA32::AppendToBuffer(const char* format, ...)
{
    Vector<char> buf = tmp_buffer_ + tmp_buffer_pos_;
    va_list args;
    va_start(args, format);
    int result = OS::VSNPrintF(buf, format, args);
    va_end(args);
    tmp_buffer_pos_ += result;
}

int DisassemblerIA32::PrintRightOperandHelper(
        byte* modrmp,
        RegisterNameMapping register_name)
{
    int mod, regop, rm;
    get_modrm(*modrmp, &mod, &regop, &rm);
    switch (mod) {
    case 0:
        if (rm == ebp) {
            int32_t disp = *reinterpret_cast<int32_t*>(modrmp+1);
            AppendToBuffer("[0x%x]", disp);
            return 5;
        } else if (rm == esp) {
            byte sib = *(modrmp + 1);
            int scale, index, base;
            get_sib(sib, &scale, &index, &base);
            if (index == esp && base == esp && scale == 0 /*times_1*/) {
                AppendToBuffer("[%s]", (this->*register_name)(rm));
                return 2;
            } else if (base == ebp) {
                int32_t disp = *reinterpret_cast<int32_t*>(modrmp + 2);
                AppendToBuffer("[%s*%d+0x%x]",
                               (this->*register_name)(index),
                               1 << scale,
                               disp);
                return 6;
            } else if (index != esp && base != ebp) {
                // [base+index*scale]
                AppendToBuffer("[%s+%s*%d]",
                               (this->*register_name)(base),
                               (this->*register_name)(index),
                               1 << scale);
                return 2;
            } else {
                UnimplementedInstruction();
                return 1;
            }
        } else {
            AppendToBuffer("[%s]", (this->*register_name)(rm));
            return 1;
        }
        break;
    case 1:  // fall through
    case 2:
        if (rm == esp) {
            byte sib = *(modrmp + 1);
            int scale, index, base;
            get_sib(sib, &scale, &index, &base);
            int disp =
                    mod == 2 ? *reinterpret_cast<int32_t*>(modrmp + 2) : *(modrmp + 2);
            if (index == base && index == rm /*esp*/ && scale == 0 /*times_1*/) {
                AppendToBuffer("[%s+0x%x]", (this->*register_name)(rm), disp);
            } else {
                AppendToBuffer("[%s+%s*%d+0x%x]",
                               (this->*register_name)(base),
                               (this->*register_name)(index),
                               1 << scale,
                               disp);
            }
            return mod == 2 ? 6 : 3;
        } else {
            // No sib.
            int disp =
                    mod == 2 ? *reinterpret_cast<int32_t*>(modrmp + 1) : *(modrmp + 1);
            AppendToBuffer("[%s+0x%x]", (this->*register_name)(rm), disp);
            return mod == 2 ? 5 : 2;
        }
        break;
    case 3:
        AppendToBuffer("%s", (this->*register_name)(rm));
        return 1;
    default:
        UnimplementedInstruction();
        return 1;
    }
}


int DisassemblerIA32::PrintRightOperand(byte* modrmp) {
    return PrintRightOperandHelper(modrmp, &DisassemblerIA32::NameOfCPURegister);
}


int DisassemblerIA32::PrintRightByteOperand(byte* modrmp) {
    return PrintRightOperandHelper(modrmp,
                                   &DisassemblerIA32::NameOfByteCPURegister);
}


// Returns number of bytes used including the current *data.
// Writes instruction's mnemonic, left and right operands to 'tmp_buffer_'.
int DisassemblerIA32::PrintOperands(const char* mnem,
                                    OperandOrder op_order,
                                    byte* data) {
    byte modrm = *data;
    int mod, regop, rm;
    get_modrm(modrm, &mod, &regop, &rm);
    int advance = 0;
    switch (op_order) {
    case REG_OPER_OP_ORDER: {
            AppendToBuffer("%s %s,", mnem, NameOfCPURegister(regop));
            advance = PrintRightOperand(data);
            break;
        }
    case OPER_REG_OP_ORDER: {
            AppendToBuffer("%s ", mnem);
            advance = PrintRightOperand(data);
            AppendToBuffer(",%s", NameOfCPURegister(regop));
            break;
        }
    default:
        UNREACHABLE();
        break;
    }
    return advance;
}


// Returns number of bytes used by machine instruction, including *data byte.
// Writes immediate instructions to 'tmp_buffer_'.
int DisassemblerIA32::PrintImmediateOp(byte* data) {
    bool sign_extension_bit = (*data & 0x02) != 0;
    byte modrm = *(data+1);
    int mod, regop, rm;
    get_modrm(modrm, &mod, &regop, &rm);
    const char* mnem = "Imm???";
    switch (regop) {
    case 0: mnem = "add"; break;
    case 1: mnem = "or"; break;
    case 2: mnem = "adc"; break;
    case 4: mnem = "and"; break;
    case 5: mnem = "sub"; break;
    case 6: mnem = "xor"; break;
    case 7: mnem = "cmp"; break;
    default: UnimplementedInstruction();
    }
    AppendToBuffer("%s ", mnem);
    int count = PrintRightOperand(data+1);
    if (sign_extension_bit) {
        AppendToBuffer(",0x%x", *(data + 1 + count));
        return 1 + count + 1 /*int8*/;
    } else {
        AppendToBuffer(",0x%x", *reinterpret_cast<int32_t*>(data + 1 + count));
        return 1 + count + 4 /*int32_t*/;
    }
}


// Returns number of bytes used, including *data.
int DisassemblerIA32::F7Instruction(byte* data) {
    assert(*data == 0xF7);
    byte modrm = *(data+1);
    int mod, regop, rm;
    get_modrm(modrm, &mod, &regop, &rm);
    if (mod == 3 && regop != 0) {
        const char* mnem = NULL;
        switch (regop) {
        case 2: mnem = "not"; break;
        case 3: mnem = "neg"; break;
        case 4: mnem = "mul"; break;
        case 7: mnem = "idiv"; break;
        default: UnimplementedInstruction();
        }
        AppendToBuffer("%s %s", mnem, NameOfCPURegister(rm));
        return 2;
    } else if (mod == 3 && regop == eax) {
        int32_t imm = *reinterpret_cast<int32_t*>(data+2);
        AppendToBuffer("test %s,0x%x", NameOfCPURegister(rm), imm);
        return 6;
    } else if (regop == eax) {
        AppendToBuffer("test ");
        int count = PrintRightOperand(data+1);
        int32_t imm = *reinterpret_cast<int32_t*>(data+1+count);
        AppendToBuffer(",0x%x", imm);
        return 1+count+4 /*int32_t*/;
    } else {
        UnimplementedInstruction();
        return 2;
    }
}

int DisassemblerIA32::D1D3C1Instruction(byte* data) {
    byte op = *data;
    assert(op == 0xD1 || op == 0xD3 || op == 0xC1);
    byte modrm = *(data+1);
    int mod, regop, rm;
    get_modrm(modrm, &mod, &regop, &rm);
    int imm8 = -1;
    int num_bytes = 2;
    if (mod == 3) {
        const char* mnem = NULL;
        if (op == 0xD1) {
            imm8 = 1;
            switch (regop) {
            case edx: mnem = "rcl"; break;
            case edi: mnem = "sar"; break;
            case esp: mnem = "shl"; break;
            default: UnimplementedInstruction();
            }
        } else if (op == 0xC1) {
            imm8 = *(data+2);
            num_bytes = 3;
            switch (regop) {
            case edx: mnem = "rcl"; break;
            case esp: mnem = "shl"; break;
            case ebp: mnem = "shr"; break;
            case edi: mnem = "sar"; break;
            default: UnimplementedInstruction();
            }
        } else if (op == 0xD3) {
            switch (regop) {
            case esp: mnem = "shl"; break;
            case ebp: mnem = "shr"; break;
            case edi: mnem = "sar"; break;
            default: UnimplementedInstruction();
            }
        }
        assert(mnem != NULL);
        AppendToBuffer("%s %s,", mnem, NameOfCPURegister(rm));
        if (imm8 > 0) {
            AppendToBuffer("%d", imm8);
        } else {
            AppendToBuffer("cl");
        }
    } else {
        UnimplementedInstruction();
    }
    return num_bytes;
}


// Returns number of bytes used, including *data.
int DisassemblerIA32::JumpShort(byte* data) {
    assert(*data == 0xEB);
    byte b = *(data+1);
    byte* dest = data + static_cast<int8_t>(b) + 2;
    AppendToBuffer("jmp %s", NameOfAddress(dest));
    return 2;
}


// Returns number of bytes used, including *data.
int DisassemblerIA32::JumpConditional(byte* data, const char* comment) {
    assert(*data == 0x0F);
    byte cond = *(data+1) & 0x0F;
    byte* dest = data + *reinterpret_cast<int32_t*>(data+2) + 6;
    const char* mnem = jump_conditional_mnem[cond];
    AppendToBuffer("%s %s", mnem, NameOfAddress(dest));
    if (comment != NULL) {
        AppendToBuffer(", %s", comment);
    }
    return 6;  // includes 0x0F
}


// Returns number of bytes used, including *data.
int DisassemblerIA32::JumpConditionalShort(byte* data, const char* comment) {
    byte cond = *data & 0x0F;
    byte b = *(data+1);
    byte* dest = data + static_cast<int8_t>(b) + 2;
    const char* mnem = jump_conditional_mnem[cond];
    AppendToBuffer("%s %s", mnem, NameOfAddress(dest));
    if (comment != NULL) {
        AppendToBuffer(", %s", comment);
    }
    return 2;
}


// Returns number of bytes used, including *data.
int DisassemblerIA32::SetCC(byte* data) {
    assert(*data == 0x0F);
    byte cond = *(data+1) & 0x0F;
    const char* mnem = set_conditional_mnem[cond];
    AppendToBuffer("%s ", mnem);
    PrintRightByteOperand(data+2);
    return 3;  // includes 0x0F
}


// Returns number of bytes used, including *data.
int DisassemblerIA32::FPUInstruction(byte* data) {
    byte b1 = *data;
    byte b2 = *(data + 1);
    if (b1 == 0xD9) {
        const char* mnem = NULL;
        switch (b2) {
        case 0xE8: mnem = "fld1"; break;
        case 0xEE: mnem = "fldz"; break;
        case 0xE1: mnem = "fabs"; break;
        case 0xE0: mnem = "fchs"; break;
        case 0xF8: mnem = "fprem"; break;
        case 0xF5: mnem = "fprem1"; break;
        case 0xF7: mnem = "fincstp"; break;
        case 0xE4: mnem = "ftst"; break;
        }
        if (mnem != NULL) {
            AppendToBuffer("%s", mnem);
            return 2;
        } else if ((b2 & 0xF8) == 0xC8) {
            AppendToBuffer("fxch st%d", b2 & 0x7);
            return 2;
        } else {
            int mod, regop, rm;
            get_modrm(*(data+1), &mod, &regop, &rm);
            const char* mnem = "?";
            switch (regop) {
            case eax: mnem = "fld_s"; break;
            case ebx: mnem = "fstp_s"; break;
            default: UnimplementedInstruction();
            }
            AppendToBuffer("%s ", mnem);
            int count = PrintRightOperand(data + 1);
            return count + 1;
        }
    } else if (b1 == 0xDD) {
        if ((b2 & 0xF8) == 0xC0) {
            AppendToBuffer("ffree st%d", b2 & 0x7);
            return 2;
        } else {
            int mod, regop, rm;
            get_modrm(*(data+1), &mod, &regop, &rm);
            const char* mnem = "?";
            switch (regop) {
            case eax: mnem = "fld_d"; break;
            case ebx: mnem = "fstp_d"; break;
            default: UnimplementedInstruction();
            }
            AppendToBuffer("%s ", mnem);
            int count = PrintRightOperand(data + 1);
            return count + 1;
        }
    } else if (b1 == 0xDB) {
        int mod, regop, rm;
        get_modrm(*(data+1), &mod, &regop, &rm);
        const char* mnem = "?";
        switch (regop) {
        case eax: mnem = "fild_s"; break;
        case edx: mnem = "fist_s"; break;
        case ebx: mnem = "fistp_s"; break;
        default: UnimplementedInstruction();
        }
        AppendToBuffer("%s ", mnem);
        int count = PrintRightOperand(data + 1);
        return count + 1;
    } else if (b1 == 0xDF) {
        if (b2 == 0xE0) {
            AppendToBuffer("fnstsw_ax");
            return 2;
        }
        int mod, regop, rm;
        get_modrm(*(data+1), &mod, &regop, &rm);
        const char* mnem = "?";
        switch (regop) {
        case ebp: mnem = "fild_d"; break;
        case edi: mnem = "fistp_d"; break;
        default: UnimplementedInstruction();
        }
        AppendToBuffer("%s ", mnem);
        int count = PrintRightOperand(data + 1);
        return count + 1;
    } else if (b1 == 0xDC || b1 == 0xDE) {
        bool is_pop = (b1 == 0xDE);
        if (is_pop && b2 == 0xD9) {
            AppendToBuffer("fcompp");
            return 2;
        }
        const char* mnem = "FP0xDC";
        switch (b2 & 0xF8) {
        case 0xC0: mnem = "fadd"; break;
        case 0xE8: mnem = "fsub"; break;
        case 0xC8: mnem = "fmul"; break;
        case 0xF8: mnem = "fdiv"; break;
        default: UnimplementedInstruction();
        }
        AppendToBuffer("%s%s st%d", mnem, is_pop ? "p" : "", b2 & 0x7);
        return 2;
    } else if (b1 == 0xDA && b2 == 0xE9) {
        const char* mnem = "fucompp";
        AppendToBuffer("%s", mnem);
        return 2;
    }
    AppendToBuffer("Unknown FP instruction");
    return 2;
}


// Mnemonics for instructions 0xF0 byte.
// Returns NULL if the instruction is not handled here.
static const char* F0Mnem(byte f0byte)
{
    switch (f0byte)
    {
    case 0xA2: return "cpuid";
    case 0x31: return "rdtsc";
    case 0xBE: return "movsx_b";
    case 0xBF: return "movsx_w";
    case 0xB6: return "movzx_b";
    case 0xB7: return "movzx_w";
    case 0xAF: return "imul";
    case 0xA5: return "shld";
    case 0xAD: return "shrd";
    case 0xAB: return "bts";
    default: return NULL;
    }
}


// Disassembled instruction '*instr' and writes it into 'out_buffer'.
int DisassemblerIA32::InstructionDecode(Vector<char> out_buffer,
                                        byte* instr) {
    tmp_buffer_pos_ = 0;  // starting to write as position 0
    byte* data = instr;
    // Check for hints.
    const char* branch_hint = NULL;
    // We use these two prefixes only with branch prediction
    if (*data == 0x3E /*ds*/) {
        branch_hint = "predicted taken";
        data++;
    } else if (*data == 0x2E /*cs*/) {
        branch_hint = "predicted not taken";
        data++;
    }
    bool processed = true;  // Will be set to false if the current instruction
    // is not in 'instructions' table.
    const InstructionDesc& idesc = instruction_table.Get(*data);
    switch (idesc.type) {
    case ZERO_OPERANDS_INSTR:
        AppendToBuffer(idesc.mnem);
        data++;
        break;

    case TWO_OPERANDS_INSTR:
        data++;
        data += PrintOperands(idesc.mnem, idesc.op_order_, data);
        break;

    case JUMP_CONDITIONAL_SHORT_INSTR:
        data += JumpConditionalShort(data, branch_hint);
        break;

    case REGISTER_INSTR:
        AppendToBuffer("%s %s", idesc.mnem, NameOfCPURegister(*data & 0x07));
        data++;
        break;

    case MOVE_REG_INSTR: {
            byte* addr = reinterpret_cast<byte*>(*reinterpret_cast<int32_t*>(data+1));
            AppendToBuffer("mov %s,%s",
                           NameOfCPURegister(*data & 0x07),
                           NameOfAddress(addr));
            data += 5;
            break;
        }

    case CALL_JUMP_INSTR: {
            byte* addr = data + *reinterpret_cast<int32_t*>(data+1) + 5;
            AppendToBuffer("%s %s", idesc.mnem, NameOfAddress(addr));
            data += 5;
            break;
        }

    case SHORT_IMMEDIATE_INSTR: {
            byte* addr = reinterpret_cast<byte*>(*reinterpret_cast<int32_t*>(data+1));
            AppendToBuffer("%s eax, %s", idesc.mnem, NameOfAddress(addr));
            data += 5;
            break;
        }

    case NO_INSTR:
        processed = false;
        break;

    default:
        UNIMPLEMENTED();  // This type is not implemented.
    }
    //----------------------------
    if (!processed) {
        switch (*data) {
        case 0xC2:
            AppendToBuffer("ret 0x%x", *reinterpret_cast<uint16_t*>(data+1));
            data += 3;
            break;

        case 0x69:  // fall through
        case 0x6B:
            { int mod, regop, rm;
                get_modrm(*(data+1), &mod, &regop, &rm);
                int32_t imm =
                        *data == 0x6B ? *(data+2) : *reinterpret_cast<int32_t*>(data+2);
                AppendToBuffer("imul %s,%s,0x%x",
                               NameOfCPURegister(regop),
                               NameOfCPURegister(rm),
                               imm);
                data += 2 + (*data == 0x6B ? 1 : 4);
            }
            break;

        case 0xF6:
            { int mod, regop, rm;
                get_modrm(*(data+1), &mod, &regop, &rm);
                if (mod == 3 && regop == eax) {
                    AppendToBuffer("test_b %s,%d", NameOfCPURegister(rm), *(data+2));
                } else {
                    UnimplementedInstruction();
                }
                data += 3;
            }
            break;

        case 0x81:  // fall through
        case 0x83:  // 0x81 with sign extension bit set
            data += PrintImmediateOp(data);
            break;

        case 0x0F:
            { byte f0byte = *(data+1);
                const char* f0mnem = F0Mnem(f0byte);
                if (f0byte == 0xA2 || f0byte == 0x31) {
                    AppendToBuffer("%s", f0mnem);
                    data += 2;
                } else if ((f0byte & 0xF0) == 0x80) {
                    data += JumpConditional(data, branch_hint);
                } else if (f0byte == 0xBE || f0byte == 0xBF || f0byte == 0xB6 ||
                           f0byte == 0xB7 || f0byte == 0xAF) {
                    data += 2;
                    data += PrintOperands(f0mnem, REG_OPER_OP_ORDER, data);
                } else if ((f0byte & 0xF0) == 0x90) {
                    data += SetCC(data);
                } else {
                    data += 2;
                    if (f0byte == 0xAB || f0byte == 0xA5 || f0byte == 0xAD) {
                        // shrd, shld, bts
                        AppendToBuffer("%s ", f0mnem);
                        int mod, regop, rm;
                        get_modrm(*data, &mod, &regop, &rm);
                        data += PrintRightOperand(data);
                        if (f0byte == 0xAB) {
                            AppendToBuffer(",%s", NameOfCPURegister(regop));
                        } else {
                            AppendToBuffer(",%s,cl", NameOfCPURegister(regop));
                        }
                    } else {
                        UnimplementedInstruction();
                    }
                }
            }
            break;

        case 0x8F:
            { data++;
                int mod, regop, rm;
                get_modrm(*data, &mod, &regop, &rm);
                if (regop == eax) {
                    AppendToBuffer("pop ");
                    data += PrintRightOperand(data);
                }
            }
            break;

        case 0xFF:
            { data++;
                int mod, regop, rm;
                get_modrm(*data, &mod, &regop, &rm);
                const char* mnem = NULL;
                switch (regop) {
                case esi: mnem = "push"; break;
                case eax: mnem = "inc"; break;
                case ecx: mnem = "dec"; break;
                case edx: mnem = "call"; break;
                case esp: mnem = "jmp"; break;
                default: mnem = "???";
                }
                AppendToBuffer("%s ", mnem);
                data += PrintRightOperand(data);
            }
            break;

        case 0xC7:  // imm32, fall through
        case 0xC6:  // imm8
            { bool is_byte = *data == 0xC6;
                data++;
                AppendToBuffer("%s ", is_byte ? "mov_b" : "mov");
                data += PrintRightOperand(data);
                int32_t imm = is_byte ? *data : *reinterpret_cast<int32_t*>(data);
                AppendToBuffer(",0x%x", imm);
                data += is_byte ? 1 : 4;
            }
            break;

        case 0x80:
            { data++;
                AppendToBuffer("%s ", "cmpb");
                data += PrintRightOperand(data);
                int32_t imm = *data;
                AppendToBuffer(",0x%x", imm);
                data++;
            }
            break;

        case 0x88:  // 8bit, fall through
        case 0x89:  // 32bit
            { bool is_byte = *data == 0x88;
                int mod, regop, rm;
                data++;
                get_modrm(*data, &mod, &regop, &rm);
                AppendToBuffer("%s ", is_byte ? "mov_b" : "mov");
                data += PrintRightOperand(data);
                AppendToBuffer(",%s", NameOfCPURegister(regop));
            }
            break;

        case 0x66:  // prefix
            data++;
            if (*data == 0x8B) {
                data++;
                data += PrintOperands("mov_w", REG_OPER_OP_ORDER, data);
            } else if (*data == 0x89) {
                data++;
                int mod, regop, rm;
                get_modrm(*data, &mod, &regop, &rm);
                AppendToBuffer("mov_w ");
                data += PrintRightOperand(data);
                AppendToBuffer(",%s", NameOfCPURegister(regop));
            } else {
                UnimplementedInstruction();
            }
            break;

        case 0xFE:
            { data++;
                int mod, regop, rm;
                get_modrm(*data, &mod, &regop, &rm);
                if (mod == 3 && regop == ecx) {
                    AppendToBuffer("dec_b %s", NameOfCPURegister(rm));
                } else {
                    UnimplementedInstruction();
                }
                data++;
            }
            break;

        case 0x68:
            AppendToBuffer("push 0x%x", *reinterpret_cast<int32_t*>(data+1));
            data += 5;
            break;

        case 0x6A:
            AppendToBuffer("push 0x%x", *reinterpret_cast<int8_t*>(data + 1));
            data += 2;
            break;

        case 0xA8:
            AppendToBuffer("test al,0x%x", *reinterpret_cast<uint8_t*>(data+1));
            data += 2;
            break;

        case 0xA9:
            AppendToBuffer("test eax,0x%x", *reinterpret_cast<int32_t*>(data+1));
            data += 5;
            break;

        case 0xD1:  // fall through
        case 0xD3:  // fall through
        case 0xC1:
            data += D1D3C1Instruction(data);
            break;

        case 0xD9:  // fall through
        case 0xDA:  // fall through
        case 0xDB:  // fall through
        case 0xDC:  // fall through
        case 0xDD:  // fall through
        case 0xDE:  // fall through
        case 0xDF:
            data += FPUInstruction(data);
            break;

        case 0xEB:
            data += JumpShort(data);
            break;

        case 0xF2:
            if (*(data+1) == 0x0F) {
                byte b2 = *(data+2);
                if (b2 == 0x11) {
                    AppendToBuffer("movsd ");
                    data += 3;
                    int mod, regop, rm;
                    get_modrm(*data, &mod, &regop, &rm);
                    data += PrintRightOperand(data);
                    AppendToBuffer(",%s", NameOfXMMRegister(regop));
                } else if (b2 == 0x10) {
                    data += 3;
                    int mod, regop, rm;
                    get_modrm(*data, &mod, &regop, &rm);
                    AppendToBuffer("movsd %s,", NameOfXMMRegister(regop));
                    data += PrintRightOperand(data);
                } else {
                    const char* mnem = "?";
                    switch (b2) {
                    case 0x2A: mnem = "cvtsi2sd"; break;
                    case 0x58: mnem = "addsd"; break;
                    case 0x59: mnem = "mulsd"; break;
                    case 0x5C: mnem = "subsd"; break;
                    case 0x5E: mnem = "divsd"; break;
                    }
                    data += 3;
                    int mod, regop, rm;
                    get_modrm(*data, &mod, &regop, &rm);
                    if (b2 == 0x2A) {
                        AppendToBuffer("%s %s,", mnem, NameOfXMMRegister(regop));
                        data += PrintRightOperand(data);
                    } else {
                        AppendToBuffer("%s %s,%s",
                                       mnem,
                                       NameOfXMMRegister(regop),
                                       NameOfXMMRegister(rm));
                        data++;
                    }
                }
            } else {
                UnimplementedInstruction();
            }
            break;

        case 0xF3:
            if (*(data+1) == 0x0F && *(data+2) == 0x2C) {
                data += 3;
                data += PrintOperands("cvttss2si", REG_OPER_OP_ORDER, data);
            } else {
                UnimplementedInstruction();
            }
            break;

        case 0xF7:
            data += F7Instruction(data);
            break;

        default:
            UnimplementedInstruction();
        }
    }

    if (tmp_buffer_pos_ < sizeof tmp_buffer_) {
        tmp_buffer_[tmp_buffer_pos_] = '\0';
    }

    int instr_len = data - instr;
    ASSERT(instr_len > 0);  // Ensure progress.

    int outp = 0;
    // Instruction bytes.
    for (byte* bp = instr; bp < data; bp++) {
        outp += OS::SNPrintF(out_buffer + outp,
                                           "%02x",
                                           *bp);
    }
    for (int i = 6 - instr_len; i >= 0; i--) {
        outp += OS::SNPrintF(out_buffer + outp,
                                           "  ");
    }

    outp += OS::SNPrintF(out_buffer + outp,
                                       " %s",
                                       tmp_buffer_.start());
    return instr_len;
}


//------------------------------------------------------------------------------


static const char* cpu_regs[8] =
{
    "eax", "ecx", "edx", "ebx", "esp", "ebp", "esi", "edi",
};


static const char* byte_cpu_regs[8] =
{
    "al", "cl", "dl", "bl", "ah", "ch", "dh", "bh",
};


static const char* xmm_regs[8] =
{
    "xmm0", "xmm1", "xmm2", "xmm3", "xmm4", "xmm5", "xmm6", "xmm7",
};


const char* NameConverter::NameOfAddress(byte* addr) const
{
    static EmbeddedVector<char, 32> tmp_buffer;
    OS::SNPrintF(tmp_buffer, "%p", addr);
    return tmp_buffer.start();
}


const char* NameConverter::NameOfConstant(byte* addr) const
{
    return NameOfAddress(addr);
}


const char* NameConverter::NameOfCPURegister(int reg) const
{
    if (0 <= reg && reg < 8) return cpu_regs[reg];
    return "noreg";
}


const char* NameConverter::NameOfByteCPURegister(int reg) const
{
    if (0 <= reg && reg < 8) return byte_cpu_regs[reg];
    return "noreg";
}


const char* NameConverter::NameOfXMMRegister(int reg) const
{
    if (0 <= reg && reg < 8) return xmm_regs[reg];
    return "noxmmreg";
}


const char* NameConverter::NameInCode(byte* addr) const
{
    // IA32 does not embed debug strings at the moment.
    UNREACHABLE();
    return "";
}


//------------------------------------------------------------------------------

Disassembler::Disassembler(const NameConverter& converter)
        : converter_(converter)
{}


Disassembler::~Disassembler()
{}


int Disassembler::InstructionDecode(Vector<char> buffer,
                                    byte* instruction)
{
    DisassemblerIA32 d(converter_, false /*do not crash if unimplemented*/);
    return d.InstructionDecode(buffer, instruction);
}


// The IA-32 assembler does not currently use constant pools.
int Disassembler::ConstantPoolSizeAt(byte* instruction)
{ return -1; }


void Disassembler::Disassemble(FILE* f, byte* begin, byte* end)
{
    NameConverter converter;
    Disassembler d(converter);
    for (byte* pc = begin; pc < end;) {
        EmbeddedVector<char, 128> buffer;
        buffer[0] = '\0';
        byte* prev_pc = pc;
        pc += d.InstructionDecode(buffer, pc);
        fprintf(f, "%p", prev_pc);
        fprintf(f, "    ");

        for (byte* bp = prev_pc; bp < pc; bp++) {
            fprintf(f, "%02x",  *bp);
        }
        for (int i = 6 - (pc - prev_pc); i >= 0; i--) {
            fprintf(f, "  ");
        }
        fprintf(f, "  %s\n", buffer.start());
    }
}

} // namespace l8
